---
description: GraphQL Code Generator's server preset, `@eddeee888/gcg-typescript-resolver-files`, helps GraphQL APIs work at any scale by enforcing best practices such as type-safety and schema module conventions.
---

import { Callout, Tabs } from '@theguild/components'

# Guide: GraphQL Yoga / Apollo Server with Server Preset

GraphQL Code Generator's server preset, `@eddeee888/gcg-typescript-resolver-files`, helps GraphQL APIs work at any scale by enforcing best practices such as type-safety and schema module conventions.

## Guide

A GraphQL API such as GraphQL Yoga or Apollo Server is the central system where many teams develop their own features without blocking other teams. However, teams may have different standards and practices that can lead to friction. The server preset has features to help solve these issues:

- **Type safety:** Resolvers are strictly generated and typed to eliminate the chance of unimplemented resolvers.
- **Schema module conventions:** These conventions make ownership clear at domain and code levels to help teams focus.

### Setup

#### 1. Create Schema Modules

The server preset works best when the schema is split into smaller modules. This approach keeps each module small and maintainable. So, instead of one schema file, you can split it into smaller schema modules:

```text
├── src/
│   ├── schema/
│   │   ├── base/
│   │   │   ├── schema.graphql
│   │   ├── user/
│   │   │   ├── schema.graphql
│   │   ├── book/
│   │   │   ├── schema.graphql
```

Here's the content of each schema module:

```graphql
# src/schema/base/schema.graphql
type Query
type Mutation

# src/schema/user/schema.graphql
extend type Query {
  user(id: ID!): User
}
type User {
  id: ID!
  fullName: String!
  isAdmin: Boolean!
}

# src/schema/book/schema.graphql
extend type Query {
  book(id: ID!): Book
}
extend type Mutation {
  markBookAsRead(id: ID!): Book!
}
type Book {
  id: ID!
  isbn: String!
}
```

#### 2. Install Server Preset

```sh npm2yarn
npm i -D @graphql-codegen/cli @eddeee888/gcg-typescript-resolver-files
```

#### 3. Configure Codegen Config

Create or update your Codegen config as follows:

<Tabs items={['codegen.ts','codegen.ts (GraphQL Yoga with File Uploads)','codegen.yml']}>

<Tabs.Tab>

```ts filename="codegen.ts"
import type { CodegenConfig } from '@graphql-codegen/cli'
import { defineConfig } from '@eddeee888/gcg-typescript-resolver-files'

const config: CodegenConfig = {
  schema: '**/schema.graphql',
  generates: {
    'src/schema': defineConfig()
  }
}
export default config
```

</Tabs.Tab>

<Tabs.Tab>

```ts filename="codegen.ts"
import type { CodegenConfig } from '@graphql-codegen/cli'
import { defineConfig } from '@eddeee888/gcg-typescript-resolver-files'

const config: CodegenConfig = {
  schema: '**/schema.graphql',
  generates: {
    'src/schema': defineConfig({
      // The following config is designed to work with GraphQL Yoga's File uploads feature
      // https://the-guild.dev/graphql/yoga-server/docs/features/file-uploads
      scalarsOverrides: {
        File: { type: 'File' }
      },
      resolverGeneration: {
        query: '*',
        mutation: '*',
        subscription: '*',
        scalar: '!*.File',
        object: '*',
        union: '',
        interface: ''
      }
    })
  }
}
export default config
```

</Tabs.Tab>

<Tabs.Tab>

```yaml filename="codegen.yml"
schema: '**/schema.graphql'
generates:
  src/schema:
    preset: '@eddeee888/gcg-typescript-resolver-files'
    watchPattern: '**/*.mappers.ts'
```

</Tabs.Tab>

</Tabs>

<Callout type="info">

**Remove existing `@graphql-codegen/typescript` and `@graphql-codegen/typescript-resolvers` config**

The server preset comes with these plugins built-in with stricter defaults. You can override the defaults using the [typesPluginsConfig option](https://github.com/eddeee888/graphql-code-generator-plugins/tree/master/packages/typescript-resolver-files#config).

</Callout>

### Generate Files

Now, run codegen:

```sh
npx graphql-codegen
```

This results in the following structure:

```
├── src/
│   ├── schema/
│   │   ├── base/
│   │   │   ├── schema.graphql
│   │   ├── user/
│   │   │   ├── resolvers/
│   │   │   │   ├── Query/
│   │   │   │   │   ├── user.ts            # Generated, changes not overwritten by codegen
│   │   │   │   ├── User.ts                # Generated, changes not overwritten by codegen
│   │   │   ├── schema.graphql
│   │   ├── book/
│   │   │   ├── resolvers/
│   │   │   │   ├── Query/
│   │   │   │   │   ├── book.ts            # Generated, changes not overwritten by codegen
│   │   │   │   ├── Mutation/
│   │   │   │   │   ├── markBookAsRead.ts  # Generated, changes not overwritten by codegen
│   │   │   │   ├── Book.ts                # Generated, changes not overwritten by codegen
│   │   │   ├── schema.graphql
│   │   ├── resolvers.generated.ts         # Entirely generated by codegen
│   │   ├── typesDefs.generated.ts         # Entirely generated by codegen
│   │   ├── types.generated.ts             # Entirely generated by codegen
```

#### Generated Files Overview

- `types.generated.ts`: TypeScript types generated by `@graphql-codegen/typescript` and `@graphql-codegen/typescript-resolvers`
- `typeDefs.generated.ts`: Static TypeScript Schema AST to be used by the server
- `user/resolvers/Query/user.ts`, `book/resolvers/Query/book.ts`, `book/resolvers/Mutation/markBookAsRead.ts`: Typed operation resolvers of each module
- `user/resolvers/User.ts`, `book/resolvers/Book.ts`: Typed object type resolvers of each module
- `resolvers.generated.ts`: Resolver map that contains all generated operation and object type resolvers

#### Integration With GraphQL API

We can use generated files in GraphQL API implementation:

<Tabs items={['GraphQL Yoga','Apollo Server']}>

<Tabs.Tab>

```ts filename="src/server.ts"
import { createYoga, createSchema } from 'graphql-yoga'
import { createServer } from 'http'
import { typeDefs } from './schema/typeDefs.generated'
import { resolvers } from './schema/resolvers.generated'

const yoga = createYoga({ schema: createSchema({ typeDefs, resolvers }) })
const server = createServer(yoga)
server.listen(3000)
```

</Tabs.Tab>

<Tabs.Tab>

```ts filename="src/server.ts"
import { ApolloServer } from 'apollo-server'
import { typeDefs } from './schema/typeDefs.generated'
import { resolvers } from './schema/resolvers.generated'

const server = new ApolloServer({ typeDefs, resolvers })

// The `listen` method launches a web server
server.listen().then(({ url }) => {
  console.log(`🚀 Server ready at ${url}`)
})
```

</Tabs.Tab>

</Tabs>

#### Implementing Resolvers

Operation resolvers are generated like this example:

```ts filename="src/schema/user/resolvers/Query/user.ts"
import type { QueryResolvers } from './../../../types.generated'
export const user: NonNullable<QueryResolvers['user']> = async (_parent, _arg, _ctx) => {
  /* Implement Query.user resolver logic here */
}
```

<Callout type="info">

**Generated operation resolvers always fail TypeScript check without implementation.**

This is intentional because it eliminates unimplemented resolvers at runtime.

</Callout>

Object type resolvers are generated like this example:

```ts filename="src/schema/user/resolvers/User.ts"
import type { UserResolvers } from './../../types.generated'
export const User: UserResolvers = {
  /* Implement User resolver logic here */
}
```

All operation and object type resolvers are automatically put into the generated resolver map:

```ts filename="src/schema/resolvers.generated.ts" {6,7,11,17}
/* This file was automatically generated. DO NOT UPDATE MANUALLY. */
import type { Resolvers } from './types.generated'
import { book as Query_book } from './book/resolvers/Query/book'
import { markBookAsRead as Mutation_markBookAsRead } from './book/resolvers/Mutation/markBookAsRead'
import { Book } from './book/resolvers/Book'
import { user as Query_user } from './user/resolvers/Query/user'
import { User } from './user/resolvers/User'
export const resolvers: Resolvers = {
  Query: {
    book: Query_book,
    user: Query_user
  },
  Mutation: {
    markBookAsRead: Mutation_markBookAsRead
  },

  Book: Book,
  User: User
}
```

The server preset handles all resolver types and imports. So, you only need to implement your resolver logic.

### Conventions to Support Schema Modules

<Callout type="info">

**The server preset has module-centric conventions**

This means instead of updating the `codegen.ts` config file, you make changes in each module. This keeps the GraphQL API maintainable at any scale.

Read more about this concept on our blog: [Scalable APIs with GraphQL Server Codegen Preset](https://the-guild.dev/blog/scalable-apis-with-graphql-server-codegen-preset)

</Callout>

<Callout type="info">

**All conventions are customizable**

Check out the [documentation](https://github.com/eddeee888/graphql-code-generator-plugins/tree/master/packages/typescript-resolver-files#config) for more options.

</Callout>

#### Adding Custom GraphQL Scalars

GraphQL does not have a lot of scalars by default. Luckily, [graphql-scalars](https://github.com/Urigo/graphql-scalars) has an extensive list of scalars.

The server preset automatically uses scalar implementation from `graphql-scalars` if it finds a matching name.

First, install `graphql-scalars`:

```sh npm2yarn
npm i graphql-scalars
```

Then, add a scalar to your schema:

```graphql filename="src/schema/base.graphql" {4,5}
type Query
type Mutation

# https://github.com/Urigo/graphql-scalars/blob/master/src/scalars/iso-date/DateTime.ts
scalar DateTime
```

Running codegen automatically imports the scalar implementation into the resolver map:

```ts filename="src/schema/resolvers.generated.ts" {7,19}
import type { Resolvers } from './types.generated'
import { book as Query_book } from './book/resolvers/Query/book'
import { markBookAsRead as Mutation_markBookAsRead } from './book/resolvers/Mutation/markBookAsRead'
import { Book } from './book/resolvers/Book'
import { user as Query_user } from './user/resolvers/Query/user'
import { User } from './user/resolvers/User'
import { DateTimeResolver } from 'graphql-scalars'
export const resolvers: Resolvers = {
  Query: {
    book: Query_book,
    user: Query_user
  },
  Mutation: {
    markBookAsRead: Mutation_markBookAsRead
  },

  Book: Book,
  User: User,
  DateTime: DateTimeResolver
}
```

Furthermore, the type is updated to use the recommended type from `graphql-scalars`:

```ts filename="src/schema/types.generated.ts" {9}
// ... other generated types

export type Scalars = {
  ID: { input: string; output: string | number }
  String: { input: string; output: string }
  Boolean: { input: boolean; output: boolean }
  Int: { input: number; output: number }
  Float: { input: number; output: number }
  DateTime: { input: Date | string; output: Date | string } // Type comes from graphql-scalars
}

// ... other generated types
```

The type of any custom scalar is `any` by default. Without the server preset, you have to configure the `DateTime` type by manually updating `codegen.ts`.

#### Adding Mappers To Chain Resolvers

By default, the generated types make resolvers return objects that match the schema types. However, this means we must handle all field mapping in the root-level resolvers.

This is where we can use [mappers](https://the-guild.dev/graphql/codegen/plugins/typescript/typescript-resolvers#use-your-model-types-mappers) to enable resolver chaining. When a mapper is used this way, it can be returned in one resolver, and become the `parent` argument in the next resolver in the chain.

With the server preset, you can add mappers by exporting interfaces or types with `Mapper` suffix from `*.mappers.ts` files in appropriate modules:

```ts filename="src/schema/user/schema.mappers.ts"
export interface UserMapper {
  id: string
  firstName: string
  lastName: string
  isAdmin: 'YES' | 'NO'
}
```

Running codegen again does a few things:

1. automatically imports and uses this mapper in schema types

```ts filename="src/schema/types.generated.ts" {2,6,11}
// ... other imports
import { UserMapper } from './user/schema.mappers'

export type ResolversTypes = {
  // ... other types
  User: ResolverTypeWrapper<UserMapper>
}

export type ResolversParentTypes = {
  // ... other types
  User: UserMapper
}
```

2. automatically compares schema type and mapper type to create required field resolvers

```ts filename="src/schema/user/resolvers/User.ts" {3-5,7-10}
import type { UserResolvers } from './../../types.generated'
export const User: UserResolvers = {
  fullName: async (_parent, _arg, _ctx) => {
    /* User.fullName resolver is required because User.fullName exists but UserMapper.fullName does not */
  },

  isAdmin: ({ isAdmin }, _arg, _ctx) => {
    /* User.isAdmin resolver is required because User.isAdmin and UserMapper.isAdmin are not compatible */
    return isAdmin
  }
}
```

You can now update your resolvers to use the mapper interface:

```ts
// src/schema/user/resolvers/Query/user.ts
import type { QueryResolvers } from './../../../types.generated'
export const user: NonNullable<QueryResolvers['user']> = async (_parent, _arg, _ctx) => {
  return { id: '001', firstName: 'Bart', lastName: 'Simpson', isAdmin: 'YES' }
}

// src/schema/user/resolvers/User.ts
import type { UserResolvers } from './../../types.generated'
export const User: UserResolvers = {
  fullName: ({ firstName, lastName }) => `${firstName} ${lastName}`
  isAdmin: ({ isAdmin }) => isAdmin === 'YES'
}
```
